﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace Wireframe
{
    /// <summary>
    ///     Represents collection of event handlers.
    /// </summary>
    public sealed class EventHandlers
    {
        private const string MethodNameStart = "Handle";
        private const string MethodNameEnd = "Event";

        /// <summary>
        ///     Collection of event handlers and callback methods.
        /// </summary>
        private readonly ConcurrentDictionary<Type, ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>>> _handlers =
            new ConcurrentDictionary<Type, ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>>>();

        /// <summary>
        ///     Initializes new instance of event handlers collection.
        /// </summary>
        /// <param name="assemblies">Collection of assemblies with event handlers.</param>
        public EventHandlers(IEnumerable<Assembly> assemblies)
        {
            if (assemblies == null)
            {
                throw new ArgumentNullException("assemblies");
            }

            var query = assemblies
                .SelectMany(p => p.GetTypes())
                .Select(p => new
                    {
                        HandlingInstanceType = p,
                        MetaMethods = p.GetMethods()
                                       .Select(q => new
                                           {
                                               MethodInfo = q,
                                               Parameters = q.GetParameters()
                                           })
                                       .Where(q => q.MethodInfo.Name.StartsWith(MethodNameStart) && q.MethodInfo.Name.EndsWith(MethodNameEnd) &&
                                                   q.Parameters.Length == 1)
                    });

            foreach (var entry in query)
            {
                foreach (var metaMethod in entry.MetaMethods)
                {
                    ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>> handlers = _handlers
                        .GetOrAdd(metaMethod.Parameters.Single().ParameterType, _ => new ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>>());

                    ConcurrentBag<MethodInfo> callbacks = handlers.GetOrAdd(entry.HandlingInstanceType, _ => new ConcurrentBag<MethodInfo>());

                    callbacks.Add(metaMethod.MethodInfo);
                }
            }
        }

        /// <summary>
        ///     Collection of event handlers.
        /// </summary>
        public ConcurrentDictionary<Type, ConcurrentDictionary<Type, ConcurrentBag<MethodInfo>>> Handlers
        {
            get { return _handlers; }
        }
    }
}